# coding:utf-8
import logging
import socket
import traceback
import time

from app.modules.common.network import is_open, kill_progress_by_port

# logger = logging.getLogger(__name__)
from app.extensions import log as logger

class PortProtector:
    """
    封装占用端口号的工具类.
    实现以下两个功能
    1. 把一个具体的端口用创建socket监听器的办法占用住.
    2. 根据需要, 把已经占用的监听器停止, 让端口释放.
    3. 根据端口, 强制kill进程, 让端口强制释放.
    """

    def __init__(self, port):
        """

        Args:
            port: 需要保护(提前占用)的端口.
        """
        self._port = int(port)
        self._serversocket = None

    def protect(self):
        """
        Returns:
            True: 端口占用成功
            False:端口占用失败

        """

        try:
            self._serversocket = socket.socket(
                socket.AF_INET, socket.SOCK_STREAM)
            self._serversocket.bind(('127.0.0.1', self._port))
            self._serversocket.listen(1) 

            _is_success = True
        except:
            logger.warn("-------------保护端口失败:%s" % self._port)
            logger.warn(traceback.format_exc())
            print traceback.format_exc()
            print "-------------保护端口失败:%s" % self._port
            _is_success = False

        return _is_success

        return True

    def free(self):
        """
        Returns:
            True: 端口释放成功
            False:端口释放失败

        """

        if self._serversocket is None:
            return True

        try:
            self._serversocket.close()
            self._serversocket = None
            _is_success = True
        except:
            print traceback.format_exc()
            _is_success = False

        return _is_success

        return True

    def is_locked(self):
        if self._serversocket is None or self._port is None:
            return False
        else:
            return self._serversocket is not None


class ProtectedPortGroupPool:
    """
    管理被保护(提前占用)的端口信息.实现下面的功能.
    1. 将端口列表按照关键字分组.
    2. 实现一组端口的保护(提前占用)和释放.
    3. 将端口号所在组key做成索引,方便查询.
    """

    def __init__(self):
        self._pool = {}
        self._port_group_key_mapping = {}

    def clean(self):
        self._pool = {}
        self._port_group_key_mapping = {}

    def register_port_group(self, group_key, ports=[]):
        """
        Args:
            group_key: 为端口集合分组的关键字, 一般取app_id
            ports: 端口列表

        Returns:None
        """
        _pool_group = {}

        for port in ports:
            _pool_group[port] = None

            # 将
            self._port_group_key_mapping.setdefault(port, group_key)

        self._pool[group_key] = _pool_group

    def protect_port_group(self, group_key, force=False):
        """
        根据分组关键字找到注册的端口列表, 将它们提前占用.

        Args:
            force: 强制保护,当被保护端口处于占用状态时, 杀掉占用它的进程, 默认False
            group_key: 需要保护(提前占用)的端口分组关键字

        Returns:
            True: 成功将端口保护(提前占用)
            False: 端口保护(提前占用)失败, 端口可能已经被占用

        """
        _pool_group = self._pool[group_key]

        if _pool_group is None:
            logger.info('%s 没有找到端口列表,是否初始化正确? ' % str(group_key))
            return False

        # 保护端口之前, 先释放占用端口的进程(可以强制释放)
        self.free_port_group(group_key=group_key, force=force)

        logger.info('释放应用的端口 %s' % str(_pool_group))

        # There could be some protect going to failed, throw exception?
        for port in _pool_group:
            _pool_group[port] = None

            locker = PortProtector(port)
            locker.protect()

            _pool_group[port] = locker

        logger.info('保护应用的端口 %s' % str(_pool_group))

        return True

    def free_port_group(self, group_key, force=False):
        """
        根据分组关键字, 释放一组端口.

        Args:
            force: 强制释放端口,当尝试释放端口失败(端口被别的进程占用时), kill掉目标端口的进程.
            group_key: 需要释放的端口分组关键字

        Returns:
            True: 释放成功
            False:端口没有找到, 释放失败.

        """
        _pool_group = self._pool[group_key]

        if _pool_group is None:
            return False

        for port in _pool_group:

            logger.info('准备释放应用的端口 %s' % str(_pool_group))

            locker = _pool_group[port]

            if locker is not None and isinstance(locker, PortProtector):
                self._pool[group_key][port].free()

            _pool_group[port] = None

            if is_open(port=port) and force:
                logger.info('释放端口失败, 强制使用kill杀掉! 如果应用停止,证明把Agent自己把自己杀掉了... 端口是%s' % port)

                time.sleep(2);

                kill_progress_by_port(port=port)

        logger.info('应用的端口 %s 释放完成' % str(_pool_group))
        return True

    def is_group_been_protected(self, group_key):

        try:
            for port in self._pool[group_key]:
                _locker = self._pool[group_key][port]
                _is_locked = _locker is not None and _locker.is_locked()
                if not _is_locked:
                    return False

            return True
        except:
            return False

    def is_port_been_protected(self, port):

        try:
            return self.is_group_been_protected(self._port_group_key_mapping[port])

        except:
            return False

    def is_port_free(self, group_key):
        """

        Args:
            group_key:  需要释放的端口分组关键字

        Returns:
            遍历组内所有端口, 只要任一端口处于被占用状态.

        """
        _pool_group = self._pool[group_key]

        if _pool_group is None:
            return False

        for port in _pool_group:
            # 任意端口是占用状态的,就算整体被占用状态.
            if is_open(port=port):
                return False

        return True


protected_port_group_pool = ProtectedPortGroupPool()
